#include "map.h"
#include "../tinyxml/tinyxml.h"
#include "../utils/uncompress.h"
#include <stdexcept>

#include <iostream>
#include "../utils/base64.h"

Map::Map() 
{
	_width = _height = _version = _tileHeight = _tileWidth = 0 ;
}

void Map::addLayer(Layer l)
{
	_layers.push_back(l) ;
}

void Map::loadFromFile(const std::string& fileName)
{
	TiXmlDocument tmxmap(fileName.c_str()) ;
	if(!tmxmap.LoadFile())
	{
		std::cerr << "Error occured while loading file " << fileName << std::endl;
		std::cerr << "Error #" << tmxmap.ErrorId() << " : " << tmxmap.ErrorDesc() << std::endl;
		exit(1);
	}
	
	TiXmlNode *mapNode = tmxmap.FirstChild("map");
	TiXmlElement* mapElem = mapNode->ToElement();
	
	// Lecture des attributs de la carte
	mapElem->Attribute("version", &_version);
	mapElem->Attribute("width", &_width);
	mapElem->Attribute("height", &_height);
	mapElem->Attribute("tilewidth", &_tileWidth);
	mapElem->Attribute("tileheight", &_tileHeight);
	
	// Read the orientation
	std::string orientationStr = mapElem->Attribute("orientation");
	
	if (!orientationStr.compare("orthogonal")) 
	{
 		_orientation = ORTHOGONAL ;
	} 
	else if (!orientationStr.compare("isometric")) 
	{
 		_orientation = ISOMETRIC ;
	}
	else if (!orientationStr.compare("hexagonal")) 
	{
		_orientation = HEXAGONAL ;
	}
	
	// Read the map properties.
	TiXmlNode *propertiesNode = mapElem->FirstChild("properties");
	if (propertiesNode) 
	{
		const TiXmlNode *propertyNode = propertiesNode->FirstChild("property");
		std::string propertyName;
		std::string propertyValue;
		
		while (propertyNode) 
		{
			const TiXmlElement* propertyElem = propertyNode->ToElement();
			
			// Read the attributes of the property and add it to the map
			propertyName = std::string(propertyElem->Attribute("name"));
			propertyValue = std::string(propertyElem->Attribute("value"));
			_properties.insert(std::pair<std::string, std::string>(propertyName, propertyValue)) ;
			
			propertyNode = propertiesNode->IterateChildren("property", propertyNode);
		}
	}
	
	// Iterate through all of the tileset elements.
	TiXmlNode *tilesetNode = mapNode->FirstChild("tileset");
	while (tilesetNode) 
	{
		// Parse the image.
		const TiXmlNode *tileNode = tilesetNode->FirstChild("tile");
		while(tileNode != NULL)
		{
			int id = -1 ;
			uint transColor = 0xFFFFFF ;
			const char* data = NULL ;
            const char* format = NULL ;
			
			const TiXmlElement* tileElem = tileNode->ToElement() ;
			tileElem->Attribute("id", &id) ;

			const TiXmlNode *imageNode = tileElem->FirstChild("image");
			if(imageNode)
			{
				const TiXmlElement *imageElem = imageNode->ToElement() ;
				format = imageElem->Attribute("format") ;
				imageElem->QueryUnsignedAttribute("trans", &transColor) ;
				
				const TiXmlNode *imageDataNode = imageElem->FirstChild("data") ;
				if(imageDataNode)
				{
					const TiXmlElement* imageDataElem = imageDataNode->ToElement();
					
					data = imageDataElem->GetText() ;
				}
			}
			_ressources.insert(std::pair<int, Tile>(id, Tile(data, format, transColor))) ;
			
			tileNode = tilesetNode->IterateChildren("tile", tileNode);
			
		}
		
 		tilesetNode = mapNode->IterateChildren("tileset", tilesetNode);
	}
	
	// Iterate through all of the layer elements.
	TiXmlNode *layerNode = mapNode->FirstChild("layer");
	while (layerNode) 
	{
		const TiXmlElement *layerElem = layerNode->ToElement();
		
		const char* name = NULL ;
		int width = 0 ;
		int height = 0 ;
		float opacity = 1 ;
		bool visible = true ;
		
		// Read the attributes.
		name = layerElem->Attribute("name");
		
		layerElem->Attribute("width", &width);
		layerElem->Attribute("height", &height);
		
		const char *opacityStr = layerElem->Attribute("opacity");
		if (opacityStr) 
		{
			opacity = (float)atof(opacityStr);
		}
		
		const char *visibleStr = layerElem->Attribute("visible");
		if (visibleStr) 
		{
			visible = atoi(visibleStr) != 0 ;
		}
		
		const TiXmlNode *dataNode = layerNode->FirstChild("data");
		const TiXmlElement *dataElem = dataNode->ToElement();
		
		std::string decode_data = base64_decode(dataElem->GetText()) ;
		
		const char* uncompress_data = decompress(decode_data.c_str(), decode_data.length()) ;
		const unsigned char *data = reinterpret_cast<const unsigned char*>(uncompress_data) ;
		int x = 1;
		int y = 1;
		
		Layer l ;
		
		for (int i = 0; i < (width * height)*4 - 3; i += 4) {
            unsigned int gid = 	data[i] | data[i + 1] << 8 | data[i + 2] << 16 |  data[i + 3] << 24 ;

            if(gid == 0)
                l.addCase(x, y, MapCase());
			else
                l.addCase(x, y, MapCase(&_ressources.at(gid-1)));

            x++;
			if (x == width+1) {
				x = 1;
				y++;
			}
		}
        l.setName(name) ;
        l.setOpacity(opacity);
        l.setVisible(visible);
		_layers.push_back(l) ;

 		layerNode = mapNode->IterateChildren("layer", layerNode);
	}
	
	// Iterate through all of the objectgroup elements.
	TiXmlNode *objectGroupNode = mapNode->FirstChild("objectgroup");
	while (objectGroupNode) 
	{
		const TiXmlElement *objectGroupElem = objectGroupNode->ToElement();
		
		const char* objectGroupName = NULL ;

		objectGroupName = objectGroupElem->Attribute("name");
		
		const TiXmlNode *objectNode = objectGroupNode->FirstChild("object");
		while (objectNode) 
		{
			const TiXmlElement *objectElem = objectNode->ToElement();
			
			const char* name = NULL ;
			const char* type = NULL ;
			int x = -1 ;
			int y = -1 ;
			int width = -1 ;
			int height = -1 ;
			int gid = 0 ;
			
			name = objectElem->Attribute("name");
			type = objectElem->Attribute("type");
			
			objectElem->Attribute("x", &x);
			objectElem->Attribute("y", &y);
			objectElem->Attribute("width", &width);
			objectElem->Attribute("height", &height);
			objectElem->Attribute("gid", &gid);
			
			Object o(objectGroupName, name, x, y, width, height) ;
			if(gid != 0)
				o.setTile(_ressources.at(gid-1));
			o.setType(type);
			
			_objects.push_back(o);
			
			objectNode = objectGroupNode->IterateChildren("object", objectNode);
			
		}
		
		objectGroupNode = mapNode->IterateChildren("objectgroup", objectGroupNode);
	}
}

int Map::version() const
{
	return _version ;
}

int Map::width() const
{
	return _width ;
}

int Map::height() const
{
	return _height ;
}

int Map::tileWidth() const
{
	return _tileWidth ;
}

int Map::tileHeight() const
{
	return _tileHeight ;
}

TmxOrientation Map::orientation() const
{
	return _orientation ;
}

const std::vector<Layer>& Map::layers() const
{
	return _layers ;
}

const std::vector<Object>& Map::objects() const 
{
	return _objects ;
}

const std::map<int, Tile>& Map::ressources() const 
{
	return _ressources ;
}

const std::map<std::string, std::string>& Map::properties() const
{
	return _properties ;
}
